#include "md5Model.h"


/*
================
	cMd5Model
================
*/
cMd5Model::cMd5Model():m_modelID(""),m_currAnim("initial"),m_iFrame( 1 )
{
	m_pMd5Model = new Md5Model;
	vecSkeleton.clear();
	vecInterpSkeleton.clear();

	D3DXMatrixIdentity( &m_matWorld );
}

cMd5Model::~cMd5Model()
{
	delete m_pMd5Model;
}

void cMd5Model::OnInit( string modelfile, string keyname )
{
	m_pDevice9 = GetDevice9();
	m_pShaderMgr = GetShaderMgr();
	m_pTextureMgr = GetTextureMgr();
	m_pModelMgr = GetModelMgr();

	m_modelID = keyname;

	m_pModelMgr->PushModel( modelfile, m_modelID );

	m_pMd5Model = m_pModelMgr->GetModel( m_modelID );

	vecSkeleton.resize( m_pMd5Model->md5mesh->numJoints );
	vecInterpSkeleton.resize( m_pMd5Model->md5mesh->numJoints );

	CreateMesh();
	CreateMaterial();
}

void cMd5Model::Update( float dt )
{
	static float elaspedTime = 0.0f;
	elaspedTime += dt;

	bool update = false;
	WrapUpdate( m_currAnim, update, elaspedTime );


	if( update ){
		for( int i = 0; i != vecMesh.size(); ++i )
		{
			Mesh* pMesh = &m_pMd5Model->md5mesh->vecMesh[ i ];
			GenerateMeshVert( pMesh, i, vecSkeleton );
		}
	}
}

void cMd5Model::Render()
{
	m_pDevice9->BeginScene();

	ID3DXEffect* pEffect = m_pShaderMgr->GetShader( "model" );
	HR( pEffect->SetTechnique( "ModelTech" ) );
	ConfigWorldMatrix();
	HR( pEffect->SetMatrix( "gWVP",&(m_matWorld * GetViewProj() ) ) );

	UINT pass;
	HR( pEffect->Begin( &pass, 0 ) );
	HR( pEffect->BeginPass( 0 ) );
	string texKey, shader;
	for( int i = 0; i != vecMesh.size(); ++i ){
		texKey = m_pMd5Model->GetShader( i );
		shader = m_pModelMgr->GetMeshShader( texKey );
		if( shader != ""  ){
			HR( pEffect->SetTexture( "gTex0", m_pTextureMgr->GetTex( texKey ) ) );
		}

		HR( pEffect->CommitChanges() );
		vecMesh[ i ]->DrawSubset( 0 );
	}

	HR( pEffect->EndPass() );
	HR( pEffect->End() );
	m_pDevice9->EndScene();
}

void cMd5Model::Release()
{
	for(int i = 0;i!=vecMesh.size();++i)
	{
		SAFE_RELEASE(vecMesh[i]);
	}
	vecMesh.clear();
	SAFE_DELETE(m_pMd5Model);
}

void cMd5Model::CreateMesh()
{
	Mesh* pMesh = NULL;

	Md5Mesh* pMd5Mesh = m_pMd5Model->md5mesh;

	vecMesh.resize( pMd5Mesh->numMeshes );
	for( int i = 0; i != pMd5Mesh->numMeshes; ++i )
	{
		pMesh = &pMd5Mesh->vecMesh[ i ];
		HR(D3DXCreateMeshFVF( pMesh->numTris, pMesh->numVerts, D3DXMESH_MANAGED|D3DXMESH_32BIT,
			CommonFVF, m_pDevice9, &vecMesh[ i ]) );

		GenerateMeshVert( pMesh, i, m_pMd5Model->md5mesh->vecJoint );

		DWORD* pIndex = NULL;
		DWORD* pAttribute = NULL;

		HR( vecMesh[ i ]->LockIndexBuffer(0,(void**)&pIndex) );
		HR( vecMesh[ i ]->LockAttributeBuffer(0,&pAttribute) );

		for( int j = 0; j != pMesh->numTris; ++j ){
			pAttribute[ j ] = 0;
			pIndex[ j * 3 ] = pMesh->vecTris[ j ].vertIndex1;
			pIndex[ j * 3 + 1 ] = pMesh->vecTris[ j ].vertIndex2;
			pIndex[ j * 3 + 2 ] = pMesh->vecTris[ j ].vertIndex3;
		}

		HR( vecMesh[ i ]->UnlockIndexBuffer() );
		HR( vecMesh[ i ]->UnlockAttributeBuffer() );
	}
}

void cMd5Model::CreateMaterial()
{
	Md5Mesh* pMesh = m_pMd5Model->md5mesh;
	string shader;
	WCHAR wShader[255];
	for( int i = 0; i != pMesh->numMeshes; ++i )
	{
		if( (shader = m_pModelMgr->GetMeshShader( pMesh->vecMesh[ i ].shader ) ) != ""){
			shader = MD5PATH + shader;
			wsprintf( wShader, L"%S", shader.c_str() );
			m_pTextureMgr->PushTex ( wShader, pMesh->vecMesh[ i ].shader);
		}
	}
}

void cMd5Model::GenerateMeshVert( Mesh* & pMesh, int iMesh, vector<Joint>& skeleton )
{
	Md5Mesh* pMd5Mesh = m_pMd5Model->md5mesh;

	CommonVertex* pVertex = NULL;
	HR( vecMesh[ iMesh ]->LockVertexBuffer( 0, (void**)&pVertex) );
	for(int j = 0; j != pMesh->numVerts; ++j )
	{
		D3DXVECTOR3 vec3( 0.0f, 0.0f, 0.0f );
		for( int k = 0; k != pMesh->vecVerts[j].weightElem; ++k )
		{
			Weight* weight = &pMesh->vecWeights[ pMesh->vecVerts[ j ].weightIndex + k ];
			Joint* joint = &skeleton[ weight->jointIndex ];
			
			D3DXVECTOR4 final;
			D3DXVec3Transform(&final,&weight->pos,&joint->matOrient);

			vec3.x += ( joint->pos.x + final.x ) * weight->weightValue;
			vec3.y += ( joint->pos.y + final.y ) * weight->weightValue;
			vec3.z += ( joint->pos.z + final.z ) * weight->weightValue;
		}

		swap(vec3.y,vec3.z);
		pVertex[ j ].pos = vec3;
		pVertex[ j ].tex = pMesh->vecVerts[ j ].texUV;
	}

	HR( vecMesh[ iMesh ]->UnlockVertexBuffer() );
}

void cMd5Model::Animate( string animname )
{
	static string preAnimname = "initial";
	string key = animname + ".md5anim";

	Md5Anim* pMd5Anim = m_pMd5Model->GetAnim( key );
	if( preAnimname == animname ){
		m_iFrame = (m_iFrame + 1) % pMd5Anim->numFrames;
		//update pos to the anim offset
		if( m_iFrame == 1 )
		{
			m_vecPos += pMd5Anim->vecOffset.back() * scalefactor * m_vecDir;
		}
	}
	else{ 
		//update pos to the frame offset
		string prekey = preAnimname + ".md5anim";
		Md5Anim* preMd5Anim = m_pMd5Model->GetAnim( prekey );
		m_vecPos += preMd5Anim->vecOffset[ m_iFrame ] * m_vecDir;
		m_iFrame = 1; 
	}
	InterpolateSkeleton( m_iFrame - 1, m_iFrame, animname );
	preAnimname = animname;

}
void cMd5Model::SetCurrAnim( string animname )
{
	m_currAnim = animname;
}
void cMd5Model::SetWorldMatrix( D3DXMATRIX matWorld )
{
	m_matWorld = matWorld;
}
void cMd5Model::SetModelID( string modelID )
{
	m_modelID = modelID;
}
void cMd5Model::SetPos( D3DXVECTOR3 pos )
{
	m_vecPos = pos;
}
void cMd5Model::SetDir( D3DXVECTOR3 dir ){
	m_vecDir = dir;
}
void cMd5Model::SetAngle( float angle ){
	m_fAngle = angle;
}

void cMd5Model::GenerateFrameSkeleton( int iFrame, string key, vector<Joint>& skeleton )
{
	
	Md5Mesh* pMd5Mesh = m_pMd5Model->md5mesh;
	Md5Anim* pMd5Anim = m_pMd5Model->mapAnim[ key ];

	vector<float>& animatedComp = pMd5Anim->vecFrame[ iFrame ].vecAnimatedComp;

	for( int j = 0; j != pMd5Anim->numJoints; ++j )
	{
		Baseframe* pBaseJoint = &pMd5Anim->vecBaseframe[ j ];
		Hierachy* pHierachy = &pMd5Anim->vecHierachy[ j ];

		D3DXVECTOR3 animatedPos = pBaseJoint->pos;
		D3DXQUATERNION animateOrient = pBaseJoint->orient;

		int index = 0;
		if( pHierachy->numComp & 1 ){
			animatedPos.x = animatedComp[ pHierachy->frameIndex + index++ ];
		}

		if( pHierachy->numComp & 2 ){
			animatedPos.y = animatedComp[ pHierachy->frameIndex + index++ ];
		}

		if( pHierachy->numComp & 4 ){
			animatedPos.z = animatedComp[ pHierachy->frameIndex + index++ ];
		}

		if( pHierachy->numComp & 8){
			animateOrient.x = animatedComp[ pHierachy->frameIndex + index++ ];
		}

		if( pHierachy->numComp & 16){
			animateOrient.y = animatedComp[ pHierachy->frameIndex + index++ ];
		}

		if( pHierachy->numComp & 32 ){
			animateOrient.z = animatedComp[ pHierachy->frameIndex + index++ ];
		}

		ComputeQuaternionW( animateOrient );

		Joint* pThisJoint = &skeleton[ j ];
		pThisJoint->parentIndex = pHierachy->parentIndex;
		pThisJoint->bonename = pHierachy->jointName;

		if( pThisJoint->parentIndex < 0 ) {
			pThisJoint->pos = animatedPos;
			pThisJoint->orient = animateOrient;

			QuatToMatrix( pThisJoint->orient, pThisJoint->matOrient );
		}
		else {
			Joint* pParentJoint = &skeleton[ pThisJoint->parentIndex ];

			D3DXVECTOR4 rotPos;
			D3DXVec3Transform( &rotPos, &animatedPos, &pParentJoint->matOrient );

			pThisJoint->pos.x = rotPos.x + pParentJoint->pos.x;
			pThisJoint->pos.y = rotPos.y + pParentJoint->pos.y;
			pThisJoint->pos.z = rotPos.z + pParentJoint->pos.z;

			QuatMulQuat( animateOrient, pParentJoint->orient, pThisJoint->orient );
			QuatNormalize( pThisJoint->orient, pThisJoint->orient );

			QuatToMatrix( pThisJoint->orient ,pThisJoint->matOrient);
		}
		if( j == 0 )
		{
			pMd5Anim->vecOffset[ iFrame ] = D3DXVec3Length( &pThisJoint->pos );
		}
	}
}

void cMd5Model::InterpolateSkeleton( int currFrame, int nextFrame, string animname )
{
	string key = animname + ".md5anim";
	if( currFrame < 0 ){
		currFrame = m_pMd5Model->mapAnim[ key ]->numFrames - 1;
		GenerateFrameSkeleton( currFrame, key, vecSkeleton );	
		return;
	}
	GenerateFrameSkeleton( currFrame, key, vecSkeleton );	
	GenerateFrameSkeleton( nextFrame, key, vecInterpSkeleton );

	float interp = 0.5f;
	for( int i = 0; i != vecSkeleton.size(); ++i )
	{
		vecSkeleton[ i ].pos = ( 1 - interp ) * vecSkeleton[ i ].pos + interp * vecInterpSkeleton[ i ].pos;
		D3DXQuaternionSlerp( &vecSkeleton[ i ].orient, &vecSkeleton[ i ].orient, &vecInterpSkeleton[ i ].orient, interp );

		QuatToMatrix( vecSkeleton[ i ].orient, vecSkeleton[ i ].matOrient );
	}
	return;
}

void cMd5Model::ConfigWorldMatrix()
{
	D3DXMATRIX matT, matR, matS;
	D3DXMatrixIdentity( &m_matWorld );	
	D3DXMatrixIdentity( &matT );
	D3DXMatrixIdentity( &matR );
	D3DXMatrixIdentity( &matS );

	D3DXMatrixTranslation( &matT, m_vecPos.x, m_vecPos.y, m_vecPos.z ) ;
	D3DXMatrixRotationY( &matR, m_fAngle );

	m_matWorld = matS * matR * matT;
}

void cMd5Model::WrapUpdate( string anim, bool& update, float& elapsedTime )
{
	string key = anim + ".md5anim";
	Md5Anim* pMd5Anim = m_pMd5Model->mapAnim[ key ];
	if( elapsedTime >= 1.0f / pMd5Anim->frameRate )
	{
		update = true;
		elapsedTime = 0.0f;
		Animate( anim );
		
		int index = m_iFrame == 0 ? pMd5Anim->numFrames - 1: m_iFrame - 1;
		D3DXVECTOR3 tempPos = m_vecPos + pMd5Anim->vecOffset[ index ] * m_vecDir;
		m_vecPos.y = GetTerrainHeight( tempPos.x, tempPos.z );
	}
}

/*
================
cModel:public cMd5Model
================
*/

cModel::cModel()
{

}



